import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import socket
import threading
import time
from queue import Queue

class NetworkConnectivityTool:
    def __init__(self, root):
        self.root = root
        self.root.title("网络连通性测试工具")
        self.root.geometry("700x500")
        self.root.resizable(True, True)
        
        # 设置中文字体
        self.style = ttk.Style()
        self.style.configure("TLabel", font=("SimHei", 10))
        self.style.configure("TButton", font=("SimHei", 10))
        self.style.configure("Treeview", font=("SimHei", 10))
        self.style.configure("Treeview.Heading", font=("SimHei", 10, "bold"))
        
        # 创建变量
        self.ip_file_path = tk.StringVar()
        self.port = tk.StringVar(value="80")
        self.status = tk.StringVar(value="就绪")
        self.success_count = tk.IntVar(value=0)
        self.failure_count = tk.IntVar(value=0)
        self.ip_list = []
        self.results = []
        self.queue = Queue()
        self.stop_event = threading.Event()
        
        # 创建UI组件
        self.create_widgets()
        
        # 启动GUI更新线程
        self.root.after(100, self.update_gui)
    
    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 文件选择区域
        file_frame = ttk.LabelFrame(main_frame, text="IP文件选择", padding=10)
        file_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(file_frame, text="IP文件:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        ttk.Entry(file_frame, textvariable=self.ip_file_path, width=50).grid(row=0, column=1, padx=5, pady=5)
        ttk.Button(file_frame, text="浏览...", command=self.browse_file).grid(row=0, column=2, padx=5, pady=5)
        
        # 端口设置区域
        port_frame = ttk.LabelFrame(main_frame, text="端口设置", padding=10)
        port_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(port_frame, text="测试端口:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        ttk.Entry(port_frame, textvariable=self.port, width=10).grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        ttk.Label(port_frame, text="多个端口请用逗号分隔，例如: 80,443,8080").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        
        # 按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=5)
        
        self.start_button = ttk.Button(button_frame, text="开始测试", command=self.start_test)
        self.start_button.pack(side=tk.LEFT, padx=5)
        
        self.stop_button = ttk.Button(button_frame, text="停止测试", command=self.stop_test, state=tk.DISABLED)
        self.stop_button.pack(side=tk.LEFT, padx=5)
        
        # 状态区域
        status_frame = ttk.LabelFrame(main_frame, text="状态", padding=10)
        status_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(status_frame, textvariable=self.status).pack(anchor=tk.W)
        
        # 添加成功和失败计数显示
        counts_frame = ttk.Frame(status_frame)
        counts_frame.pack(fill=tk.X, pady=2, anchor=tk.W)
        
        ttk.Label(counts_frame, text="成功:").pack(side=tk.LEFT, padx=(0, 5))
        self.success_label = ttk.Label(counts_frame, textvariable=self.success_count, foreground="green")
        self.success_label.pack(side=tk.LEFT, padx=(0, 10))
        
        ttk.Label(counts_frame, text="失败:").pack(side=tk.LEFT, padx=(0, 5))
        self.failure_label = ttk.Label(counts_frame, textvariable=self.failure_count, foreground="red")
        self.failure_label.pack(side=tk.LEFT)
        
        # 结果表格
        results_frame = ttk.LabelFrame(main_frame, text="测试结果", padding=10)
        results_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        # 创建滚动条
        scrollbar = ttk.Scrollbar(results_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 创建Treeview表格
        self.results_tree = ttk.Treeview(
            results_frame,
            columns=("ip", "port", "status", "time"),
            show="headings",
            yscrollcommand=scrollbar.set
        )
        self.results_tree.pack(fill=tk.BOTH, expand=True)
        
        # 配置滚动条
        scrollbar.config(command=self.results_tree.yview)
        
        # 设置列标题和宽度
        self.results_tree.heading("ip", text="IP地址")
        self.results_tree.heading("port", text="端口")
        self.results_tree.heading("status", text="连接状态")
        self.results_tree.heading("time", text="响应时间(ms)")
        
        self.results_tree.column("ip", width=150, anchor=tk.CENTER)
        self.results_tree.column("port", width=80, anchor=tk.CENTER)
        self.results_tree.column("status", width=120, anchor=tk.CENTER)
        self.results_tree.column("time", width=120, anchor=tk.CENTER)
    
    def browse_file(self):
        """打开文件浏览器选择IP文件"""
        file_path = filedialog.askopenfilename(
            title="选择IP文件",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if file_path:
            self.ip_file_path.set(file_path)
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    self.ip_list = [line.strip() for line in f if line.strip()]
                self.status.set(f"已加载 {len(self.ip_list)} 个IP地址")
            except Exception as e:
                messagebox.showerror("错误", f"读取文件失败: {str(e)}")
    
    def start_test(self):
        """开始测试网络连通性"""
        # 清空结果
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)
        self.results = []
        self.success_count.set(0)
        self.failure_count.set(0)
        
        # 获取端口列表
        try:
            ports = [int(port.strip()) for port in self.port.get().split(',') if port.strip()]
            if not ports:
                messagebox.showerror("错误", "请输入有效的端口号")
                return
        except ValueError:
            messagebox.showerror("错误", "端口号必须是整数")
            return
        
        # 检查是否有IP地址
        if not self.ip_list:
            if not self.ip_file_path.get():
                messagebox.showerror("错误", "请先选择IP文件")
            else:
                messagebox.showerror("错误", "IP文件中没有有效的IP地址")
            return
        
        # 更新状态并禁用/启用按钮
        self.status.set(f"正在测试 {len(self.ip_list)} 个IP地址，{len(ports)} 个端口...")
        self.start_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        self.stop_event.clear()
        
        # 创建并启动测试线程
        test_thread = threading.Thread(target=self.run_tests, args=(ports,))
        test_thread.daemon = True
        test_thread.start()
    
    def stop_test(self):
        """停止测试"""
        self.stop_event.set()
        self.status.set("正在停止测试...")
    
    def run_tests(self, ports):
        """在线程中运行测试"""
        total_tests = len(self.ip_list) * len(ports)
        completed_tests = 0
        
        for ip in self.ip_list:
            if self.stop_event.is_set():
                self.queue.put(("status", "测试已停止"))
                break
            
            for port in ports:
                if self.stop_event.is_set():
                    break
                
                start_time = time.time()
                result = self.test_connection(ip, port)
                end_time = time.time()
                
                response_time = int((end_time - start_time) * 1000)  # 转换为毫秒
                self.results.append((ip, port, result, response_time))
                
                # 判断成功或失败并更新计数
                is_success = result == "成功"
                self.queue.put(("result", (ip, port, result, response_time, is_success)))
                
                completed_tests += 1
                progress = f"已完成 {completed_tests}/{total_tests} 个测试"
                self.queue.put(("status", progress))
        
        if not self.stop_event.is_set():
            self.queue.put(("status", f"测试完成，共测试 {total_tests} 个连接"))
        
        # 重置按钮状态
        self.queue.put(("buttons",))
    
    def test_connection(self, ip, port):
        """测试与指定IP和端口的连接"""
        try:
            with socket.create_connection((ip, port), timeout=2) as sock:
                return "成功"
        except (socket.timeout, ConnectionRefusedError):
            return "失败(拒绝或超时)"
        except OSError as e:
            return f"失败({str(e)})"
        except Exception as e:
            return f"失败(未知错误: {str(e)})"
    
    def update_gui(self):
        """更新GUI界面"""
        while not self.queue.empty():
            message = self.queue.get()
            if message[0] == "result":
                ip, port, result, time, is_success = message[1]
                # 根据结果设置行颜色
                tag = "success" if is_success else "failure"
                self.results_tree.insert("", tk.END, values=(ip, port, result, time), tags=(tag,))
                # 配置行颜色
                self.results_tree.tag_configure("success", background="#d4edda")
                self.results_tree.tag_configure("failure", background="#f8d7da")
                
                # 更新成功/失败计数
                if is_success:
                    self.success_count.set(self.success_count.get() + 1)
                else:
                    self.failure_count.set(self.failure_count.get() + 1)
            elif message[0] == "status":
                self.status.set(message[1])
            elif message[0] == "buttons":
                self.start_button.config(state=tk.NORMAL)
                self.stop_button.config(state=tk.DISABLED)
        
        # 继续更新
        self.root.after(100, self.update_gui)

if __name__ == "__main__":
    root = tk.Tk()
    app = NetworkConnectivityTool(root)
    root.mainloop()    